#include "pch.h"
#include "win_hook_event.h"
#include "powertoy_module.h"
#include <mutex>
#include <deque>
#include <thread>

static std::mutex mutex;
static std::deque<WinHookEvent> hook_events;
static std::condition_variable dispatch_cv;

void intercept_system_menu_action(intptr_t);

static void CALLBACK win_hook_event_proc(HWINEVENTHOOK winEventHook,
                                         DWORD event,
                                         HWND window,
                                         LONG object,
                                         LONG child,
                                         DWORD eventThread,
                                         DWORD eventTime) {
  std::unique_lock lock(mutex);
  hook_events.push_back({ event,
                          window,
                          object,
                          child,
                          eventThread,
                          eventTime });
  lock.unlock();
  dispatch_cv.notify_one();
}

static bool running = false;
static std::thread dispatch_thread;
static void dispatch_thread_proc() {
  std::unique_lock lock(mutex);
  while (running) {
    dispatch_cv.wait(lock, []{ return !running || !hook_events.empty(); });
    if (!running)
      return;
    while (!hook_events.empty()) {
      auto event = hook_events.front();
      hook_events.pop_front();
      lock.unlock();
      intptr_t data = reinterpret_cast<intptr_t>(&event);
      intercept_system_menu_action(data);
      powertoys_events().signal_event(win_hook_event, data);
      lock.lock();
    }
  }
}

static HWINEVENTHOOK hook_handle;

void start_win_hook_event() {
  std::lock_guard lock(mutex);
  if (running)
    return;
  running = true;
  dispatch_thread = std::thread(dispatch_thread_proc);
  hook_handle = SetWinEventHook(EVENT_MIN, EVENT_MAX, nullptr, win_hook_event_proc, 0, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
}

void stop_win_hook_event() {
  std::unique_lock lock(mutex);
  if (!running)
    return;
  running = false;
  UnhookWinEvent(hook_handle);
  lock.unlock();
  dispatch_cv.notify_one();
  dispatch_thread.join();
  lock.lock();
  hook_events.clear();
  hook_events.shrink_to_fit();
}

void intercept_system_menu_action(intptr_t data) {
  WinHookEvent* evt = reinterpret_cast<WinHookEvent*>(data);
  if (evt->event == EVENT_SYSTEM_MENUSTART || evt->event == EVENT_OBJECT_INVOKED) {
    powertoys_events().handle_system_menu_action(*evt);
  }
}
